package info.niwota.frames;

import info.niwota.frames.Dialogs.ItemInfo;
import info.niwota.frames.Dialogs.ItemListAdapter;
import info.niwota.frames.KeyTracker.Stage;
import info.niwota.frames.KeyTracker.State;
import info.niwota.webapp.InstallHotwebActivity;
import info.niwota.webapp.LabelListActivity;
import info.niwota.webapp.WebIntents;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.webkit.WebView;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.Gallery;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ZoomButtonsController;

import com.admob.android.ads.AdView;

public class PhotoFrameActivity extends Activity implements
		View.OnClickListener, KeyTracker.OnKeyTracker {
	private static final boolean DEBUG = Debug.DEBUG;
	
	private SpinDialog dialog;

	private void hideProgress() {
		try {
			if (dialog != null) {
				Window d = dialog.getWindow();
				if (d != null) {
					dialog.dismiss();
				}
				dialog = null;
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void showProgress() {
		try {

			final Context ctx = PhotoFrameActivity.this;
			CharSequence msg = ctx.getText(R.string.text_please_wait);
			dialog =  SpinDialog.show(ctx, "", msg, true, true, new OnCancelListener() {	
				public void onCancel(DialogInterface dialog) {
					
					if (mTextView != null) {
						mTextView.setText(R.string.text_canceled);
					}
				}
			});
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private class InitTask extends AsyncTask<Void, Void, Void> {

		private Exception error;
		private int callback;
		private Object data;
	
		public InitTask(int callback, Object data) {
			this.callback = callback;
			this.data = data;
		}

		@Override
		protected void onCancelled() {
			hideProgress();
		}

		@Override
		protected void onPreExecute() {
			showProgress();
		}

		@Override
		protected Void doInBackground(Void... params) {
			if (Debug.DEBUG) {
				Log.d(TAG, " doInBackground loading... " +params);
			}
			try {
				load();
			} catch (Exception e) {
				error = e;
				//
				e.printStackTrace();
			}
			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			hideProgress();

			try {
				if (error != null) {
					showMessage("" + error.getMessage());
				}
				postLoad(callback, data);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {

			}
		}
	}
	
	private class MyScaleGestureListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
		@Override
        public boolean onScale(ScaleGestureDetector detector) {
        	if (!detector.isInProgress()) {
        		return false;
        	}
        	
        	float factor = detector.getScaleFactor();
        	
            scale *= factor;
            scale = Math.max(mImageView.mMinZoom, Math.min(scale, mImageView.mMaxZoom));
        	
        	final float x = detector.getFocusX();
        	final float y = detector.getFocusY();
        	
        	mImageView.zoomTo(scale, x * factor, y * factor);

            return true;
        }
    }

	private class MyGestureListener extends
			GestureDetector.SimpleOnGestureListener {
		private static final float SWIPE_MIN_X_DISTANCE = 120; // 120

		private static final float SWIPE_MIN_Y_DISTANCE = 80; // 80

		private static final int SWIPE_THRESHOLD_VELOCITY = 200; // 200

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {
			ImageViewTouch iv = mImageView;
			boolean scaled = (iv.getScale() > 1.0f);
			if (scaled) {
				// scroll
				return true;
			}
			if (DEBUG) {
				Log.d(TAG, "onFling ");
			}
			try {
				//
				float dx12 = (e1.getX() - e2.getX());
				float dx21 = (e2.getX() - e1.getX());
				float dy12 = (e1.getY() - e2.getY());
				float dy21 = (e2.getY() - e1.getY());

				float dd = (Math.abs(dx12) - Math.abs(dy12));

				if (dd > 0) {
					// left right
					if (dx12 > SWIPE_MIN_X_DISTANCE
							&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
						doSwipeLeft();
					} else if (dx21 > SWIPE_MIN_X_DISTANCE
							&& Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
						doSwipeRight();
					}
				} else {
					// up down
					if (dy12 > SWIPE_MIN_Y_DISTANCE
							&& Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
						doSwipeUp();
					} else if (dy21 > SWIPE_MIN_Y_DISTANCE
							&& Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
						doSwipeDown();
					}
				}

			} catch (Exception e) {
				// nothing
			}

			return true;
		}

		private void doSwipeDown() {
			int pos = frame.getPosition();
			int cnt = frame.getCount();
			int n = (cnt - pos) / 2 + 1;
			//
			moveNextOrPrevious(n);
		}

		private void doSwipeUp() {
			int pos = frame.getPosition();
			moveNextOrPrevious(-(pos / 2) - 1);
		}

		private void doSwipeRight() {
			moveNextOrPrevious(1);
		}

		private void doSwipeLeft() {
			moveNextOrPrevious(-1);
		}

		@Override
		public boolean onDoubleTap(MotionEvent e) {
			ImageViewTouch iv = mImageView;
			// zoom and center at the tap
			// point
			if (iv.getScale() > 1.0f) {
				// save scale if greater than 1.0f or
				// saveScaleIf(iv.getScale());
				//
				iv.zoomTo(1f);
			} else {
				float scale = Preferences.getScale(PhotoFrameActivity.this);
				iv.zoomToPoint(scale, e.getX(), e.getY());
			}
			return true;
		}

		@Override
		public void onLongPress(MotionEvent e) {
			if (mScaleGestureDetector != null && mScaleGestureDetector.isInProgress()) {
				return; //ignore
			}
			// save current scale
			ImageViewTouch iv = mImageView;
			float scale = iv.getScale();
			saveScale(scale);
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {
			ImageViewTouch iv = mImageView;
			if (iv.getScale() > 1.0f) {
				iv.postTranslateCenter(-distanceX, -distanceY);
			}
			return true;
		}

		@Override
		public boolean onSingleTapConfirmed(MotionEvent e) {
			showOnScreenControls();
			scheduleDismissOnScreenControls();
			//
			return true;
		}

		@Override
		public boolean onSingleTapUp(MotionEvent e) {
			return true;
		}
	}

	private static final int DIALOG_DELETEFRAME = 15;

	private static final int DIALOG_HELP = 10;

	private static final int DIALOG_LISTFRAME = 13;

	private static final int DIALOG_NEWFRAME = 14;

	private static final int DIALOG_PICK = 12;

	private static final int DIALOG_REMOVE = 11;
	
	private static final int DIALOG_MESSAGE = 18;

	private static final String HELP_URI = "file:///android_asset/help.html";

	/* package */static final int RC_PICK = 1;

	//private static final int RC_SAVE_SCREEN = 4;

	private static final int RC_SEND = 2;

	private static final int RC_SETTINGS = 3;

	private static final int RC_PICK_PEER_FRAME = 5;
	
	private static final int SERVICE_ID = R.string.app_name;

	private static final String TAG = "PhotoFrameActivity";

	private class ImageTask extends AsyncTask<Void, Void, Void> {
		boolean pageNo = false;
		boolean strip = false;
		int position = 0;
		
		private Exception error;

		public ImageTask(int position, boolean pageNo, boolean strip) {
			this.position = position;
			this.pageNo = pageNo;
			this.strip = strip;
		}

		@Override
		protected void onCancelled() {
			hideProgress();
		}

		@Override
		protected void onPreExecute() {
			showProgress();
		}

		@Override
		protected Void doInBackground(Void... params) {
			try {
				if (DEBUG) {
					Log.d(TAG, "position: " + position +" pageNo: " + pageNo + " strip: " + strip);
				}
				
				setImage(position);

			} catch (Exception e) {
				error = e;
			}
			return null;
		}

		@Override
		protected void onPostExecute(Void result) {
			//
			try {
				if (pageNo) {
					showPageNo();
					scheduleDismissOnScreenControls();
				}
				
				if (strip) {
					if (showStrip && galleryAdapter != null) {
						gallery.setSelection(position);
					}
				}
				
				if (error != null) {
					showMessage("" + error.getMessage());
				}
				
			} catch (Exception e) {
				e.printStackTrace();
			} finally {

			}
		}

	};
	
	private ImageTask imageTask = null;

	private static void addNotification(Context ctx) {
		try {
			Notification note = new Notification(R.drawable.icon, null, 0);
			PendingIntent pi = PendingIntent.getActivity(ctx, 0, new Intent(
					ctx, PhotoFrameActivity.class), 0);
			note.flags |= Notification.FLAG_NO_CLEAR;
			note.flags |= Notification.FLAG_ONGOING_EVENT;
			//
			CharSequence title = ctx.getText(R.string.app_name);
			CharSequence desc = ctx.getText(R.string.text_tagline);
			note.setLatestEventInfo(ctx, title, desc, pi);
			NotificationManager nm = (NotificationManager) ctx
					.getSystemService(Context.NOTIFICATION_SERVICE);
			nm.notify(SERVICE_ID, note);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private static void removeNotification(Context ctx) {
		try {
			NotificationManager nm = (NotificationManager) ctx
					.getSystemService(Context.NOTIFICATION_SERVICE);
			//
			nm.cancelAll();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	//

	private static final int CB_SHOW_IMAGE = 1;

	private static final int CB_ADD_IMAGE = 2;

	//
	/*package*/ final ImageFrame frame = new ImageFrame();

	private View mActionIconPanel;

	private final Runnable mDismissOnScreenControlRunner = new Runnable() {
		public void run() {
			hideOnScreenControls();
		}
	};

	private GestureDetector mGestureDetector;

	private ImageGetter mGetter;

	final GetterHandler mHandler = new GetterHandler();

	private final Animation mHideNextImageViewAnimation = new AlphaAnimation(
			1F, 0F);

	private final Animation mHidePrevImageViewAnimation = new AlphaAnimation(
			1F, 0F);

	private ImageViewTouch mImageView;

	private ImageButton mLockScroll;

	private View mNextImageView;

	private View mPrevImageView;

	private ImageButton mSaveScreen;

	private final Animation mShowNextImageViewAnimation = new AlphaAnimation(
			0F, 1F);

	private final Animation mShowPrevImageViewAnimation = new AlphaAnimation(
			0F, 1F);

	private TextView mTextView;

	private PowerManager.WakeLock mWakeLock;

	private ZoomButtonsController mZoomButtonsController;

	boolean paused = true;

	private TextView mTitleView;

	private Dialog diaDelete;

	private PhotoFrameImageAdapter galleryAdapter;

	private Gallery gallery;

	private boolean showStrip;

	private View mRootLayout;

	private float scale;

	private float[] matrix;

	private boolean scaled;

	private ItemListAdapter localAdapter;

	private Dialog diaMessage;

	private CharSequence message;

	private List<Uri> starred = new ArrayList<Uri>();

	protected boolean quitIfCancel;

	private int callback = CB_SHOW_IMAGE;

	private InitTask initTask;

	private ImageButton mToggleStrip;

	private ScaleGestureDetector mScaleGestureDetector;

	private void acquireLock() {
		if (DEBUG) {
			Log.v(TAG, "Acquiring screen wake lock");
		}
		if (mWakeLock != null) {
			return;
		}
		try {
			PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);

			mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK
					| PowerManager.ACQUIRE_CAUSES_WAKEUP
					| PowerManager.ON_AFTER_RELEASE, TAG);

			mWakeLock.acquire();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void refreshGallery() {
		if (galleryAdapter != null) {
			if (showStrip) {
				galleryAdapter.load(frame);
				galleryAdapter.refresh();
				gallery.setSelection(frame.getPosition());
			}
		}
	}

	private Intent createContentIntent() {
		Intent i = new Intent(Intent.ACTION_GET_CONTENT);
		i.setType("image/*");
		return i;
	}

	private Dialog createDeleteFrameDialog() {
		final Context ctx = this;
		OnClickListener cl = new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				switch (which) {
				case DialogInterface.BUTTON_POSITIVE:

					final File frameStore = frame.getStore();
					if (frameStore != null) {
						File[] files = FrameHelper.list(ctx);
						int idx = 0;
						for (int i = 0; i < files.length; i++) {
							if (files[i].equals(frameStore)) {
								idx = i;
								break;
							}
						}
						boolean del = frame.delete();

						if (del) {
							// if the current frame is the first one, move to
							// next; otherwise move to previous
							idx = (idx == 0 ? (idx + 1) : (idx - 1));
							File f = null;
							if (idx < files.length) {
								f = (files[idx]);
							} else {
								// one only one frame
								f = FrameHelper.createStore(ctx);
							}
							if (DEBUG) {
								Log.d(TAG, "prev/next file: " + f);
							}
							loadFrame(f, f.lastModified());
						}
					}
					break;
				default:
				}
			}
		};
		AlertDialog.Builder b = new AlertDialog.Builder(this);
		b.setIcon(android.R.drawable.ic_dialog_alert);
		// default title to reserve space or the title will be invisible
		b.setTitle(R.string.action_deleteframe);
		b.setMessage(R.string.text_are_your_sure);
		b.setPositiveButton(android.R.string.ok, cl);
		b.setNegativeButton(android.R.string.cancel, cl);
		//
		return b.create();
	}

	private Dialog createFramesDialog() {
		final Context ctx = this;
		localAdapter = new ItemListAdapter(this) {

			public void loadItems(final Context context, List<ItemInfo> list) {
				if (DEBUG) {
					Log.d(TAG, "load : " + list);
				}
				
				File[] files = FrameHelper.list(ctx);
				for (int i = 0; i < files.length; i++) {
					File f = files[i];
					ItemInfo item = new ItemInfo();
					item.setTitle(f.getName());
					boolean sel = f.equals(frame.getStore());
					if (sel) {
						item.setIconRes(R.drawable.btn_check_buttonless_on);
					} else {
						item.setIconRes(0);
					}
					item.setIcon(null);
					item.setObject(f);
					//
					list.add(item);
				}
			}

			public void onClick(DialogInterface dialog, int which) {
				if (DEBUG) {
					Log.d(TAG, "onClick which:  " + which);
				}
				ItemInfo item = getItem(which);
				File file = null;
				if (item != null) {
					file = (File) item.getObject();
				}
				if (DEBUG) {
					Log.d(TAG, "onClick file: " + file);
				}
				saveFrame();
				loadFrame(file, file.lastModified());
			}
		};
		CharSequence title = ctx.getText(R.string.my_frames);
		AlertDialog d = Dialogs.create(ctx, title, localAdapter);
		return d;
	}
	
	private void saveFrame() {
		frame.save(this);
		frame.clear();
		
		try {
			saveImageSetting();
			
			FrameHelper.addToStarred(this, starred);
			
			starred.clear();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void loadFrame(File file, long lastModified) {
		frame.clear();
		//
		frame.setStore(file);
		frame.setLastModified(lastModified);

		loadFrame(CB_SHOW_IMAGE, null);
	}

	private Dialog createHelpDialog() {
		LayoutInflater f = LayoutInflater.from(this);
		final View v = f.inflate(R.layout.webdia, null);

		Dialog d = new Dialog(this);
		LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
		d.requestWindowFeature(Window.FEATURE_LEFT_ICON);
		d.setContentView(v, lp);
		d.setTitle(R.string.title_help);
		d.setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_dialog_menu_generic);
		return d;
	}

	private Dialog createNewFrameDialog() {
		LayoutInflater li = LayoutInflater.from(this);
		final View v = li.inflate(R.layout.newframe, null);
		final Context ctx = this;
		final DialogInterface.OnClickListener l = new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				try {
					switch (which) {
					case DialogInterface.BUTTON_POSITIVE:
						EditText et = (EditText) v
								.findViewById(R.id.newframe_name);
						String name = et.getText().toString();
						et.setText(null);
						if (TextUtils.isEmpty(name)) {
							name = ImageFrame.DEFAULT_FRAMENAME;
						}
						//
						saveFrame();
						//
						File f = FrameHelper.createStore(ctx, name);
						loadFrame(f, f.lastModified());
						return;
					default:
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};
		AlertDialog.Builder b = new AlertDialog.Builder(this);
		b.setView(v);
		b.setTitle(R.string.action_newframe);
		b.setPositiveButton(android.R.string.ok, l);
		b.setNegativeButton(android.R.string.cancel, l);
		return b.create();
	}

	private Dialog createPickDialog() {
		Intent i = createContentIntent();
		return PickDialog.create(this, i, R.string.text_pick);
	}

	private Dialog createRemoveDialog() {
		OnClickListener cl = new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				switch (which) {
				case DialogInterface.BUTTON_POSITIVE:
					deletePicture();
					break;
				default:
				}
			}
		};
		AlertDialog.Builder b = new AlertDialog.Builder(this);
		b.setIcon(android.R.drawable.ic_dialog_alert);
		b.setTitle(R.string.action_remove);
		b.setMessage(R.string.text_remove);
		b.setPositiveButton(android.R.string.ok, cl);
		b.setNegativeButton(android.R.string.cancel, cl);
		//
		return b.create();
	}

	public boolean dispatchTouchEvent(MotionEvent m) {
		if (paused) {
			return true;
		}
		if (mZoomButtonsController.isVisible()) {
			scheduleDismissOnScreenControls();
		}
		return super.dispatchTouchEvent(m);
	}

	private void addImages(Object obj) {
		try {
//			Intent data = (Intent) obj;
//			// pick
//			Uri uri = data.getData();
//			if (DEBUG) {
//				Log.d(TAG, "addImages uri: " + uri);
//			}
//
//			// multi select
//			List<Uri> list = data
//					.getParcelableArrayListExtra(MultiSelectImageActivity.EXTRA_URIS);
//			if (DEBUG) {
//				Log.d(TAG, "addImages list: " + list);
//			}
//
//			// check list first if not null, it is multi select; otherwise check
//			// uri
//			//
//			final int currentPosition = frame.getPosition();
//			if (list != null) {
//				for (Uri i : list) {
//					frame.add(this.getContentResolver(), currentPosition, i);
//				}
//			} else {
//				if (uri != null) {
//					frame.add(this.getContentResolver(), currentPosition, uri);
//				}
//			}
			//
			Toast.makeText(this, R.string.text_added, Toast.LENGTH_SHORT)
					.show();
			//
		} catch (Exception e) {
			showError(e);
			e.printStackTrace();
		}
	}
	
	private void doImagePicked(Intent data) {
		// pick
		Uri uri = data.getData();
		if (DEBUG) {
			Log.d(TAG, "addImages uri: " + uri);
		}

		// multi select
		List<Uri> list = data
				.getParcelableArrayListExtra(MultiSelectImageActivity.EXTRA_URIS);
		if (DEBUG) {
			Log.d(TAG, "addImages list: " + list);
		}

		// check list first if not null, it is multi select; otherwise check
		// uri
		//
		File file = Preferences.getFrameFile(this);
		if (list != null) {
			FrameHelper.add(list, file);
		} else {
			if (uri != null) {
				FrameHelper.add(uri, file);
			}
		}
	}

	private void deletePicture() {
		final int currentPosition = frame.getPosition();
		if (DEBUG) {
			Log.d(TAG, "deletePicture currentPosition: " + currentPosition
					+ " frame: " + frame);
		}
		
		frame.removeCurrent();
		
		int cnt = frame.getCount();
		if (cnt == 0) {
			frame.setPosition(0);
			if (galleryAdapter != null) {
				galleryAdapter.clear();
				galleryAdapter.refresh();
				gallery.postInvalidate();
			}
			//
			showBlank();
			return;
		} else {
			if (currentPosition == cnt) {
				frame.setPosition(currentPosition - 1);
			}
		}
		//
		refreshGallery();
		//
		showImage();
	}

	private void doDeleteFrame() {
		showDialog(DIALOG_DELETEFRAME);
	}

	private void doListFrame() {
		showDialog(DIALOG_LISTFRAME);
	}

	private void doNewFrame() {
		showDialog(DIALOG_NEWFRAME);
	}

	private void doPick() {
		try {
			if (!WebshotInstallActivity.isWebshotAvailable(this)) {
				showDialog(DIALOG_PICK);
			} else {
				Intent i = createContentIntent();
				final CharSequence title = getText(R.string.text_pick);
				Intent ci = Intent.createChooser(i, title);
				startActivityForResult(ci, RC_PICK);
			}
		} catch (Exception e) {
			showError(e);
			e.printStackTrace();
		}
	}

	private void doQuit() {
		if (DEBUG) {
			Log.d(TAG, "doQuit");
		}
		removeNotification(this);
		finish();
	}

	/* package */void doRemove() {
		showDialog(DIALOG_REMOVE);
	}

	public void saveScale(float scale) {
		// force save regardless of the scale value
		Preferences.putScale(this, scale);
		boolean scaled = (scale > 1.0f);
		Preferences.putScaled(this, scaled);
		//
		this.scale = scale;
		this.scaled = scaled;
		//
		Toast.makeText(this, R.string.text_scale_saved, Toast.LENGTH_SHORT)
				.show();
	}

	private void doSettings() {
		try {
			Intent i = new Intent(this, SettingsActivity.class);
			startActivityForResult(i, RC_SETTINGS);
		} catch (Exception e) {
			// e.printStackTrace();
		}
	}

	private void doShare() {
		IImage image = frame.getCurrentImage();
		if (image != null) {
			if (image instanceof PeerUriImage) {
				try {
					File file = new File(getCacheDir(), "photo.jpg");
					CompressFormat cf = (CompressFormat.JPEG);
					Drawable d = mImageView.getDrawable();
					Util.save(this, file, d, cf);
					String type = image.getMimeType();
					startSendActivity(type, Uri.fromFile(file));
				} catch (Exception e) {
					showError(e);
				}
			} else {
				Uri uri = image.fullSizeImageUri();
				String type = image.getMimeType();
				startSendActivity(type, uri);
			}
		}
	}
	
	private void doStar() {
		try {
			IImage image = frame.getCurrentImage();
			Uri rc = save(image);
			if (DEBUG) {
				Log.d(TAG, "saved as: " + rc);
			}
			
			if (rc != null) {
				FrameHelper.ensureStarStore(this);
				starred.add(rc);
				
				showMessage(getString(R.string.text_starred, rc));
			}
			
		} catch (Exception e) {
			showError(e);
		}
	}

	private void doSave() {
		IImage image = frame.getCurrentImage();
		Uri rc = save(image);
		if (DEBUG) {
			Log.d(TAG, "saved as: " + rc);
		}
		String s = getString(R.string.text_saved, rc);
		showMessage(s);
	}
	
	private Uri save(IImage image) {
		try {
			Uri uri = image.fullSizeImageUri();
			//
			File file = Util.getOutputFile(uri, System.currentTimeMillis()+".jpg");
			
			CompressFormat cf = (CompressFormat.JPEG);
			Drawable d = mImageView.getDrawable();
			
			Util.save(this, file, d, cf);
			//
			ContentResolver cr = getContentResolver();
			String pa[] = frame.getPeerLabel();
			String title = frame.getName();
			
			Uri rc = Util.addImage(cr, file, title, pa[0] + " " + pa[1], uri.toString());
			
			return rc;
		} catch (Exception e) {
			showError(e);
		}
		return null;
	}

	void doShareFrame() {
		//ImageList allImages = frame.getAllImages();
		int cnt = frame.getCount(); //(allImages == null ? 0 : allImages.getCount());
		if (cnt > 1) {
			try {
				Intent intent = new Intent();
				intent.setAction(Intent.ACTION_SEND_MULTIPLE);
				String mimeType = "image/*";
				intent.setType(mimeType);
				
				intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, frame.getParcelableList());
				// getText(R.string.action_share))
				startActivity(Intent.createChooser(intent, getTitle()));
			} catch (Exception e) {
				showError(e);
			}
		} else if (cnt == 1) {
			try {
				IImage image = frame.getImage(0);
				Intent intent = new Intent();
				intent.setData(image.fullSizeImageUri());
				intent.setAction(Intent.ACTION_SEND);
				String mimeType = image.getMimeType();
				intent.setType(mimeType);
				intent.putExtra(Intent.EXTRA_STREAM, image.fullSizeImageUri());
				// getText(R.string.action_share)
				startActivity(Intent.createChooser(intent, getTitle()));
			} catch (Exception e) {
				showError(e);
			}
		}
	}

	private boolean hasImage() {
		boolean b = !frame.isEmpty();
		return b;
	}

	private void hideOnScreenControls() {
		View panel = mActionIconPanel;
		//
		boolean hasImage = hasImage();
		//
		if (panel.getVisibility() == View.VISIBLE && hasImage) {
			Animation a = new AlphaAnimation(1, 0);
			a.setDuration(500);
			panel.startAnimation(a);
			panel.setVisibility(View.INVISIBLE);
		}

		if (mNextImageView.getVisibility() == View.VISIBLE) {
			Animation a = mHideNextImageViewAnimation;
			a.setDuration(500);
			mNextImageView.startAnimation(a);
			mNextImageView.setVisibility(View.GONE); // INVISIBLE
		}

		if (mPrevImageView.getVisibility() == View.VISIBLE) {
			Animation a = mHidePrevImageViewAnimation;
			a.setDuration(500);
			mPrevImageView.startAnimation(a);
			mPrevImageView.setVisibility(View.GONE); // INVISIBLE
		}

		mZoomButtonsController.setVisible(false);

		if (hasImage) {
			mTitleView.setVisibility(View.GONE);
			mTextView.setVisibility(View.GONE);
		} else {
			showEmptyText();
		}

		mSaveScreen.setVisibility(View.GONE);
		mLockScroll.setVisibility(View.GONE);
	}

	private void initRes() {
		if (DEBUG) {
			Log.d(TAG, "initRes");
		}
		// admob
		boolean noezb = (FrameHelper.isEZBoardingAvailable(this) == false);
		AdView banner = (AdView) findViewById(R.id.banner);
		//
		banner.setEnabled(noezb);

		banner.setVisibility(noezb ? View.VISIBLE : View.GONE);
		if (DEBUG) {
			Log.d(TAG, "No ezb, ads enabled? " + noezb);
		}
		//
		mSaveScreen = (ImageButton) findViewById(R.id.action_save_screen);
		mLockScroll = (ImageButton) findViewById(R.id.action_lock_scroll);
		mToggleStrip = (ImageButton) findViewById(R.id.action_toggle_strip);
		
		mToggleStrip.setOnClickListener(this);

		mLockScroll.setOnClickListener(this);
		mSaveScreen.setOnClickListener(this);

		mTitleView = (TextView) findViewById(R.id.title);
		mTextView = (TextView) findViewById(R.id.text);
		mImageView = (ImageViewTouch) findViewById(R.id.image);
		mActionIconPanel = findViewById(R.id.action_icon_panel);
		View panel = mActionIconPanel;
		//
		int[] ids = { R.id.add, R.id.share, R.id.remove, R.id.save, R.id.star };
		for (int id : ids) {
			View view = panel.findViewById(id);
			view.setVisibility(View.GONE);
			view.setOnClickListener(this);
		}
		mImageView.setEnableTrackballScroll(true);
		//

		mRootLayout = findViewById(R.id.rootLayout);

		setupOnScreenControls(mRootLayout, mImageView);
		//
		Gallery g = (Gallery) findViewById(R.id.gallery);
		
		g.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View v,
					int pos, long id) {
				if (DEBUG) {
					Log.d(TAG, "onItemClick position: " + pos);
				}
				try {
					//setImage(position);
					doSetImage(pos, false, false);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		gallery = g;

		// hide all but image
		mImageView.setVisibility(View.VISIBLE);
		//
		mPrevImageView.setVisibility(View.GONE);
		mNextImageView.setVisibility(View.GONE);
		mTitleView.setVisibility(View.GONE);
		mTextView.setVisibility(View.GONE);
	}

	private void setFullScreen() {
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
	}

	private void doSetImage(int pos, boolean pageNo, boolean strip) {
		if (DEBUG) {
			Log.d(TAG, "@@@@@@@ pos: " + pos +" page no: " + pageNo + " strip: " + strip +" imageTask : " + imageTask);
		}
		try {
			if (imageTask != null) {
				imageTask.cancel(true);
			}
			//
			hideProgress();
			//
			imageTask = new ImageTask(pos, pageNo, strip);		
			imageTask.execute();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private void loadFrame(int callback, Object data) {
		if (DEBUG) {
			Log.d(TAG, "loadFrame frameStore: " + frame.getStore()
					+ " callback: " + callback + " data: " + data
					+ " initTask: " + initTask);
		}
		
		if (isFinishing()) {
			return;
		}
		
		//
		try {
			showLoading();
			//
			if (mGetter != null) {
				mGetter.cancelCurrent();
				mGetter.stop();
			}
			
			mGetter = new ImageGetter();
			//
			paused = false;

			//
			frame.clear();

			initGalleryAdapter();
			
			mImageView.clear();
			//
			initTask = new InitTask(callback, data);
			initTask.execute();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void initGalleryAdapter() {
		if (galleryAdapter == null) {
			galleryAdapter = new PhotoFrameImageAdapter(this);
		}
		galleryAdapter.clear();
	}

	private void showLoading() {
		if (mTextView != null) {
			mTextView.setTextSize(20);
			mTextView.setVisibility(View.VISIBLE);
			mTextView.setText(getText(R.string.text_loading));
		}
	}

	/**
	 * Load frame
	 * @throws Exception
	 */
	private void load() throws Exception {
		if (DEBUG) {
			Log.d(TAG,
					"load: " + frame  + " galleryAdapter: "
							+ galleryAdapter);
		}
		
		// prefs
		Context ctx = this;
		scaled = Preferences.isScaled(ctx);
		scale = Preferences.getScale(ctx);
		matrix = Preferences.getScrollXY(ctx);
		showStrip = Preferences.showFilmstrip(ctx);
		
		//
		if (galleryAdapter != null) {
			galleryAdapter.clear();
		}

		frame.restore(this);
	}

	private void postLoad(int callback, Object obj) {
		if (DEBUG) {
			Log.d(TAG, "postLoad callback: " + callback + " obj: " + obj);
		}
		initTask = null;
		//
		switch (callback) {
		case CB_SHOW_IMAGE:
			break;
		case CB_ADD_IMAGE:
			addImages(obj);
			break;
		default:
		}
		updateUi();
		//
		showImage();
	}

	private void moveNextOrPrevious(int delta) {
		final int currentPosition = frame.getPosition();

		int pos = currentPosition + delta;
		int cnt = frame.getCount();

		if (DEBUG) {
			Log.d(TAG, "*** moveNextOrPrevious pos: " + pos + " cnt: " + cnt
					+ " current Position: " + currentPosition);
		}

		if (cnt == 0) {
			return;
		}
		if (delta > 0) {
			pos = Math.min(pos, cnt - 1);
		} else {
			pos = Math.max(pos, 0);
		}
		//
		showImage(pos);
		//
		showPageNo();
		scheduleDismissOnScreenControls();
	}

	/* package */void showImage(int pos) {
		final int currentPosition = frame.getPosition();

		if (currentPosition != pos) {
			doSetImage(pos, false, true);
		}
	}
	
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (DEBUG) {
			Log.d(TAG, "onActivityResult requestCode: " + requestCode
					+ " resultCode:" + resultCode + " == " + RESULT_OK
					+ " data: " + data);
		}
		callback = CB_SHOW_IMAGE;
	
		if (resultCode == RESULT_OK) {
			switch (requestCode) {
			case RC_SETTINGS:
				final Context ctx = this;
				if (Preferences.enableStatusbar(ctx)) {
					addNotification(ctx);
				} else {
					removeNotification(ctx);
				}
				break;
			case RC_PICK:
				callback = CB_ADD_IMAGE;
				doImagePicked(data);
				break;
			case RC_PICK_PEER_FRAME:
				doPeerFramePicked(data);
				return;
			default:
			}
		}
	}

	private void doPeerFramePicked(Intent data) {
		try {
			long lastModified = data.getLongExtra(WebIntents.EXTRA_LAST_MODIFIED, 0);
			String peer = data.getStringExtra(WebIntents.EXTRA_PEER);
			String label = data.getStringExtra(WebIntents.EXTRA_LABEL);
			//
			File f = FrameHelper.getPeerStore(this, peer, label);
			
			frame.clear();
			//
			frame.setLastModified(lastModified);
			frame.setStore(f);
			
			//
			Preferences.putFrameFile(this, f);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.add:
			doPick();
			break;
		case R.id.remove:
			doRemove();
			break;
		case R.id.share:
			doShare();
			break;
		case R.id.save:
			doSave();
			break;
		case R.id.star:
			doStar();
			break;
		case R.id.next_image:
			moveNextOrPrevious(1);
			break;
		case R.id.prev_image:
			moveNextOrPrevious(-1);
			break;
		case R.id.action_save_screen:
			//Screens.blackscreen(this, RC_SAVE_SCREEN);
			break;
		case R.id.action_lock_scroll:
			break;
		case R.id.action_toggle_strip:
			toggleStrip();
			break;
		}
	}

	private void toggleStrip() {
		showStrip = !showStrip;
		Preferences.putShowFilmstrip(this, showStrip);
		if (showStrip) {
			if (galleryAdapter == null) {
				initGalleryAdapter();
			}
		}
		updateUi();
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		// seems not working with screen on lock
		if (DEBUG) {
			Log.d(TAG, "onConfigurationChanged ignored " + newConfig);
		}
	}

	@Override
	public void onCreate(Bundle instanceState) {
		if (DEBUG) {
			Log.d(TAG, "onCreate " + instanceState);
		}
		super.onCreate(instanceState);

		setFullScreen();

		setContentView(R.layout.imageholder);
		
		initRes();
		
		Intent data = getIntent();
		String peer = data.getStringExtra(WebIntents.EXTRA_PEER);
	
		if (TextUtils.isEmpty(peer)) {
			// set default store
			initFrameStore();
			//
			//loadFrame(CB_SHOW_IMAGE, null);
			callback = CB_SHOW_IMAGE;
		
			if (Preferences.enableStatusbar(this)) {
				addNotification(this);
			}
		} else {
			doPickPeerFrame(data);
		}
	}

	private void initFrameStore() {
		File frameStore = Preferences.getFrameFile(this);
		if (frameStore == null) {
			frameStore = defaultStore();
		}
		
		frame.setStore(frameStore);
		if (frame.isPeer()) {
			//TODO 
			//to avoid hang
			frameStore = defaultStore();
		}
	}
	
	private File defaultStore() {
		File[] files = FrameHelper.list(this);
		return files[0];
	}

	@Override
	protected Dialog onCreateDialog(int id) {
		switch (id) {
		case DIALOG_HELP:
			return createHelpDialog();
		case DIALOG_REMOVE:
			return createRemoveDialog();
		case DIALOG_PICK:
			return createPickDialog();
		case DIALOG_LISTFRAME:
			return createFramesDialog();
		case DIALOG_NEWFRAME:
			return createNewFrameDialog();
		case DIALOG_DELETEFRAME:
			diaDelete = createDeleteFrameDialog();
			return diaDelete;
		case DIALOG_MESSAGE:
			diaMessage = createMessageDialog();
			return diaMessage;
		default:
		}
		return null;
	}

	private Dialog createMessageDialog() {
		LayoutInflater f = LayoutInflater.from(this);
		final View v = f.inflate(R.layout.message, null);

		Dialog d = new Dialog(this);
		LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
		d.requestWindowFeature(Window.FEATURE_LEFT_ICON);
		d.setContentView(v, lp);
		d.setTitle("");
		d.setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.ic_dialog_menu_generic);
		
		return d;

	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		if (DEBUG) {
			Log.d(TAG, "onCreateOptionsMenu: " + menu);
		}
		//
		MenuInflater mif = getMenuInflater();
		mif.inflate(R.menu.imageholder_options, menu);

		return true;
	}

	@Override
	protected void onDestroy() {
		if (DEBUG) {
			Log.d(TAG, "onDestroy");
		}
		// This is necessary to make the ZoomButtonsController unregister
		// its configuration change receiver.
		ZoomButtonsController c = mZoomButtonsController;
		if (c != null) {
			c.setVisible(false);
		}
		super.onDestroy();
		//
	}

	@Override
	public boolean onMenuItemSelected(int featureId, MenuItem item) {
		boolean b = super.onMenuItemSelected(featureId, item);
		return b;
	}

	@Override
	protected void onNewIntent(Intent intent) {
		if (DEBUG) {
			Log.d(TAG, "onNewIntent " + intent);
		}
		super.onNewIntent(intent);

		//
		callback = CB_SHOW_IMAGE;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		if (DEBUG) {
			Log.d(TAG, "onOptionsItemSelected: " + item + " frameStore: "
					+ frame.getStore());
		}
		int id = item.getItemId();
		switch (id) {
		case R.id.imageholder_peers_opt_menu_id:
			doPickPeerFrame(null);
			return true;
		case R.id.imageholder_new_opt_menu_id:
			doNewFrame();
			return true;
		case R.id.imageholder_delete_opt_menu_id:
			doDeleteFrame();
			return true;
		case R.id.imageholder_listframe_opt_menu_id:
			doListFrame();
			return true;
		case R.id.imageholder_help_opt_menu_id:
			showDialog(DIALOG_HELP);
			return true;
		case R.id.imageholder_settings_opt_menu_id:
			doSettings();
			return true;
		case R.id.imageholder_quit_opt_menu_id:
			doQuit();
			return true;
		case R.id.imageholder_add_opt_menu_id:
			doPick();
			return true;
		case R.id.imageholder_remove_opt_menu_id:
			doRemove();
			return true;
		case R.id.imageholder_share_opt_menu_id:
			doShare();
			return true;
		case R.id.imageholder_save_opt_menu_id:
			doSave();
			return true;
		case R.id.imageholder_star_opt_menu_id:
			doStar();
			return true;
		case R.id.imageholder_perm_opt_menu_id:
			doPerms();
			return true;
		default:
		}
		return false;
	}

	private void doPerms() {
		try {
			Intent i = new Intent(this, LabelListActivity.class);
			startActivity(i);
		} catch (Exception e) {
			showMessage(e);
			// e.printStackTrace();
		}
	}

	private void doPickPeerFrame(Intent data) {
		try {
			if (InstallHotwebActivity.ensure(this)) {
				Intent i = new Intent(this, PickPeerFrameActivity.class);
				if (data != null) {
					i.putExtra(WebIntents.EXTRA_PEER, data.getStringExtra(WebIntents.EXTRA_PEER));
					i.putExtra(WebIntents.EXTRA_STATUS, data.getIntExtra(WebIntents.EXTRA_STATUS, 0));
				}
				startActivityForResult(i, RC_PICK_PEER_FRAME);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	protected void onPause() {
		if (DEBUG) {
			Log.d(TAG, "onPause");
		}
		try {
			releaseLock();
			//
			saveFrame();

			super.onPause();
			
			//just in case
			hideProgress();
			//
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	protected void onPrepareDialog(int id, Dialog dialog) {
		if (id == DIALOG_LISTFRAME || id == DIALOG_HELP) {
			final Window w = dialog.getWindow();
			Issues.fixIssue3674(w);
		}
		switch (id) {
		case DIALOG_LISTFRAME:
			if (localAdapter != null) {
				localAdapter.reload();
			}
			break;
		case DIALOG_DELETEFRAME:
			diaDelete.setTitle(getString(R.string.text_delete_frame,
					frame.getDisplayName()));
			break;
		case DIALOG_HELP:
			WebView wv = (WebView) dialog.findViewById(R.id.webview);
			wv.loadUrl(HELP_URI);
			wv.setFocusable(true);
			wv.setFocusableInTouchMode(true);
			break;
		case DIALOG_MESSAGE:
			TextView tv = (TextView) dialog.findViewById(R.id.text);
			tv.setText(message);
			break;
		}
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu menu) {
		super.onPrepareOptionsMenu(menu);
		if (paused) {
			return false;
		}

		boolean edit = (frame != null && frame.isEditable());
		boolean hasImg = hasImage();
		
		// delete frame - local frames only
		menu.findItem(R.id.imageholder_delete_opt_menu_id).setEnabled(edit).setVisible(true);

		//
		boolean canRemove = hasImg && edit; 
		menu.findItem(R.id.imageholder_add_opt_menu_id).setEnabled(edit).setVisible(edit);
		menu.findItem(R.id.imageholder_remove_opt_menu_id).setEnabled(canRemove).setVisible(canRemove);
		
		//peer
		boolean canKeep = hasImg && !edit; //peer image only
		menu.findItem(R.id.imageholder_save_opt_menu_id).setEnabled(canKeep).setVisible(canKeep);
		menu.findItem(R.id.imageholder_star_opt_menu_id).setEnabled(canKeep).setVisible(canKeep);
		
		menu.findItem(R.id.imageholder_share_opt_menu_id).setEnabled(hasImg);
		return true;
	}

	@Override
	protected void onRestart() {
		if (DEBUG) {
			Log.d(TAG, "onRestart");
		}
		super.onRestart();
	}

	@Override
	protected void onRestoreInstanceState(Bundle b) {
		super.onRestoreInstanceState(b);
		//
		if (DEBUG) {
			Log.d(TAG, "onRestoreInstanceState bundle: " + b);
		}
	}

	@Override
	protected void onResume() {
		if (DEBUG) {
			Log.d(TAG, "onResume");
		}
		//
		try {
			super.onResume();
			//
			acquireLock();
			//
			starred.clear();
		
			//
			loadFrame(callback, null);
			//
			callback = CB_SHOW_IMAGE;
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

//	@Override
//	public Object onRetainNonConfigurationInstance() {
//		if (DEBUG) {
//			Log.d(TAG, "onRetainNonConfigurationInstance");
//		}
//		return null;
//	}

	@Override
	public void onSaveInstanceState(Bundle b) {

		if (DEBUG) {
			Log.d(TAG, "onSaveInstanceState bundle: " + b);
		}

		super.onSaveInstanceState(b);
	}

	@Override
	public void onStart() {
		if (DEBUG) {
			Log.d(TAG, "onStart");
		}
		super.onStart();
	}

	@Override
	public void onStop() {
		if (DEBUG) {
			Log.d(TAG, "onStop");
		}
		super.onStop();
		//
		try {
			// //may not be called
			//
			//saveImageSettings();
			//
			paused = true;
			// mGetter could be null if we call finish() and leave early in
			// onStart().

			if (mGetter != null) {
				mGetter.cancelCurrent();
				mGetter.stop();
				mGetter = null;
			}

			// removing all callback in the message queue
			mHandler.removeAllGetterCallbacks();

			//
			if (galleryAdapter != null) {
				galleryAdapter.clear();
			}
			//
			hideOnScreenControls();
			mImageView.clear();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void releaseLock() {
		if (DEBUG) {
			Log.v(TAG, "Releasing wake lock");
		}
		//
		if (mWakeLock != null) {
			mWakeLock.release();
			mWakeLock = null;
		}
	}

	private void restoreImage(ImageViewTouch iv, boolean isThumb) {
		if (DEBUG) {
			Log.d(TAG, "restoreImage isThumb:" + isThumb);
		}
		// Context ctx = this;
		// boolean scaled = Preferences.isScaled(ctx);
		if (DEBUG) {
			Log.d(TAG, "scaled: " + scaled);
		}
		//
		if (scaled && iv != null) {
			// only restore scale

			// float scale = Preferences.getScale(ctx);
			// float[] matrix = Preferences.getScrollXY(ctx);
			if (DEBUG) {
				Log.d(TAG,
						"scale: " + scale + " matrix: " + Arrays.asList(matrix));
			}

			// iv.restoreImage(scale, matrix);
			iv.zoomTo(scale);
		}
	}

	private void saveImageSetting() {
		// save settings
		ImageViewTouch iv = mImageView;
		float scale = iv.getScale();
		float[] values = iv.getMatrix();
		//
		saveScaleIf(scale);
		//
		Preferences.putScrollXY(this, values);
		//
		Preferences.putFrameFile(this, frame.getStore());
	}

	private void saveScaleIf(float scale) {
		// save if scale is greater than 1.0f
		if (DEBUG) {
			Log.d(TAG, "saveScaleIf: " + scale);
		}
		boolean scaled = (scale > 1.0f);
		if (scaled) {
			Preferences.putScale(this, scale);
		}
		Preferences.putScaled(this, scaled);
	}

	private void scheduleDismissOnScreenControls() {
		mHandler.removeCallbacks(mDismissOnScreenControlRunner);
		mHandler.postDelayed(mDismissOnScreenControlRunner, 1000); // 2000
	}

	private void showImage() {
		final int currentPosition = frame.getPosition();

		if (DEBUG) {
			Log.d(TAG, "current: " + currentPosition + " showImage: "
					+ frame);
		}
		
		int cnt = frame.getCount(); //
		if (cnt == 0) {
			showBlank();
			return;
		}

		doSetImage(currentPosition, true, false);
	}

	private void updateUi() {
		final int currentPosition = frame.getPosition();
	
		if (showStrip) {
			gallery.setAdapter(galleryAdapter);
			galleryAdapter.load(frame);
			galleryAdapter.refresh();
			gallery.setSelection(currentPosition);
		}
		gallery.setVisibility(showStrip ? View.VISIBLE : View.GONE);
		gallery.invalidate();
		
		mToggleStrip.setImageResource(showStrip ? R.drawable.arrow_down : R.drawable.arrow_up);
	}

	private void setImage(final int pos) {
		// show image
		if (DEBUG) {
			Log.d(TAG, "******** setImage pos: " + pos);
		}

		// currentPosition = pos;
		frame.setPosition(pos);

		final ImageGetterCallback cb = new ImageGetterCallback() {
			public void completed() {
				if (DEBUG) {
					Log.d(TAG, "#####******** setImage ImageGetterCallback  completed pos: " + pos);
				}
				
				hideProgress();
			}

			//
			public int fullImageSizeToUse(int pos, int offset) {
				// this number should be bigger so that we can zoom. we may
				// need to get fancier and read in the fuller size image as the
				// user starts to zoom.
				// Originally the value is set to 480 in order to avoid OOM.
				// Now we set it to 2048 because of using
				// native memory allocation for Bitmaps.
				final int imageViewSize = 2048;
				return imageViewSize;
			}

			//
			public void imageLoaded(int pos, int offset, RotateBitmap bitmap,
					boolean isThumb) {
				final int currentPosition = frame.getPosition();

				// called twice thumb and full
				if (DEBUG) {
					Log.d(TAG, "******* imageLoaded current pos: " + currentPosition
							+ " pos: " + pos + " offset: " + offset
							+ " bitmap: " + bitmap + " isThumb: " + isThumb);
				}
				
				// shouldn't get here after onPause()
				//
				// We may get a result from a previous request. Ignore it.
				if (pos != currentPosition) {
					bitmap.recycle();
					return;
				}

				if (offset == 0) {
					mImageView.setImageRotateBitmapResetBase(bitmap, isThumb);
					updateZoomButtonsEnabled();
					//
					restoreImage(mImageView, isThumb);
				}
				
			}

			public int[] loadOrder() {
				return new int[] { 0 }; // {0, 1, -1}
			}

			//
			public boolean wantsFullImage(int pos, int offset) {
				return (offset == 0);
			}

			public boolean wantsThumbnail(int pos, int offset) {
				return true;
			}

			public void showError(Throwable e) {
				message = e.getLocalizedMessage();
				showDialog(DIALOG_MESSAGE);
				//Toast.makeText(PhotoFrameActivity.this, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
			}
		};

		//
		if (mGetter != null) {
			mGetter.setPosition(pos, cb, frame.getList(), mHandler);
		}
	}

	private void setupOnScreenControls(View rootView, View ownerView) {
		mNextImageView = rootView.findViewById(R.id.next_image);
		mPrevImageView = rootView.findViewById(R.id.prev_image);

		mNextImageView.setOnClickListener(this);
		mPrevImageView.setOnClickListener(this);

		setupZoomButtonController(ownerView);
		setupOnTouchListeners(rootView);
	}

	private void setupOnTouchListeners(View rootView) {
		mGestureDetector = new GestureDetector(this, new MyGestureListener());
		mScaleGestureDetector = new ScaleGestureDetector(this, new MyScaleGestureListener());
		
		// If the user touches anywhere on the panel (including the
		// next/prev button). We show the on-screen controls. In addition
		// to that, if the touch is not on the prev/next button, we
		// pass the event to the gesture detector to detect double tap.
		final OnTouchListener buttonListener = new OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				scheduleDismissOnScreenControls();
				return false;
			}
		};

		OnTouchListener rootListener = new OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {
				buttonListener.onTouch(v, event);
				mGestureDetector.onTouchEvent(event);
				mScaleGestureDetector.onTouchEvent(event);
				// We do not use the return value of
				// mGestureDetector.onTouchEvent because we will not receive
				// the "up" event if we return false for the "down" event.
				return true;
			}
		};

		mNextImageView.setOnTouchListener(buttonListener);
		mPrevImageView.setOnTouchListener(buttonListener);
		rootView.setOnTouchListener(rootListener);
	}

	private void setupZoomButtonController(final View ownerView) {
		final ZoomButtonsController c = new ZoomButtonsController(ownerView);
		c.setAutoDismissed(false);
		c.setZoomSpeed(100);
		c.setOnZoomListener(new ZoomButtonsController.OnZoomListener() {
			public void onVisibilityChanged(boolean visible) {
				if (visible) {
					updateZoomButtonsEnabled();
				}
			}

			public void onZoom(boolean zoomIn) {
				if (zoomIn) {
					mImageView.zoomIn();
				} else {
					mImageView.zoomOut();
				}
				c.setVisible(true);
				updateZoomButtonsEnabled();
			}
		});
		mZoomButtonsController = c;
	}

	private void showBlank() {
		mImageView.clear();
		//
		updateActionIcons();
		//
		showEmptyText();
		//
		mPrevImageView.setVisibility(View.GONE);
		mNextImageView.setVisibility(View.GONE);
		//
		mSaveScreen.setVisibility(View.GONE);
		mLockScroll.setVisibility(View.GONE);
	}

	private void showEmptyText() {
		mTextView.setTextSize(16);
		mTextView.setVisibility(View.VISIBLE);
		mTextView.setText(R.string.text_page_info_none);

		showTitle();
	}

	private void showError(Exception e) {
		e.printStackTrace();
		
		Toast.makeText(this, ""+e.getMessage(), Toast.LENGTH_SHORT).show();
	}

	private void showOnScreenControls() {
		if (paused) {
			return;
		}
		// If the view has not been attached to the window yet, the
		// zoomButtonControls will not able to show up. So delay it until the
		// view has attached to window.
		View panel = mActionIconPanel;
		if (panel.getWindowToken() == null) {
			mHandler.postGetterCallback(new Runnable() {
				public void run() {
					showOnScreenControls();
				}
			});
			return;
		}

		//
		updateActionIcons();
		updateNextPrevControls();
		updateZoomButtonsEnabled();
		//
		boolean b = hasImage();
		mZoomButtonsController.setVisible(b);
		//
		showTitle();
		// pagination
		if (b) {
			showPageNo();
		} else {
			showEmptyText();
		}
		//
		int vi = View.GONE; //
		mSaveScreen.setVisibility(vi);
		mLockScroll.setVisibility(vi);
		//

		panel.setVisibility(View.VISIBLE);
		if (panel.getVisibility() != View.VISIBLE) {
			//
			Animation animation = new AlphaAnimation(0, 1);
			animation.setDuration(500);
			panel.startAnimation(animation);
			panel.setVisibility(View.VISIBLE);
		}
	}

	private void showPageNo() {
		final int currentPosition = frame.getPosition();

		mTextView.setTextSize(28);
		int cnt = frame.getCount();
		String pi = getString(R.string.text_page_info, currentPosition + 1, cnt);
		mTextView.setText(pi);
		mTextView.setVisibility(cnt > 0 ? View.VISIBLE : View.GONE);
	}

	private void showTitle() {
		final String name = frame.getDisplayName();

		mTitleView.setVisibility(View.VISIBLE);
		String title = (name != null ? name : ImageFrame.DEFAULT_FRAMENAME);
		mTitleView.setText(title);
	}

	private void startSendActivity(String type, Uri uri) {
		try {
			Intent i = new Intent(this, CropImage.class);
			i.setAction(Intent.ACTION_SEND);
			i.setType(type);
			i.setData(uri);
			//
			startActivityForResult(i, RC_SEND);
		} catch (Exception e) {
			showError(e);
		}
	}

	private void updateActionIcons() {
		//
		View panel = mActionIconPanel;
		if (panel == null) {
			return;
		}

		// send
		boolean hasImg = hasImage();

		View vshare = panel.findViewById(R.id.share);
		vshare.setEnabled(hasImg);
		vshare.setVisibility(hasImg ? View.VISIBLE : View.INVISIBLE);

		boolean edit = (frame.isEditable());

		// remove
		boolean canRemove = hasImg && edit;
		View vremove = panel.findViewById(R.id.remove);
		vremove.setEnabled(canRemove);
		vremove.setVisibility(canRemove ? View.VISIBLE : View.GONE);
		
		// add
		View vadd = panel.findViewById(R.id.add);
		vadd.setEnabled(edit);
		vadd.setVisibility(edit ? View.VISIBLE : View.GONE);
		
		boolean keep = hasImg && !edit;
		// save
		View vsave = panel.findViewById(R.id.save);
		vsave.setEnabled(keep);
		vsave.setVisibility(keep ? View.VISIBLE : View.GONE);
		
		// star
		View vstar = panel.findViewById(R.id.star);
		vstar.setEnabled(keep);
		vstar.setVisibility(keep ? View.VISIBLE : View.GONE);
		
	}

	private void updateNextPrevControls() {
		final int currentPosition = frame.getPosition();
		//final ImageList allImages = frame.getAllImages();

		boolean showPrev = currentPosition > 0;
		int cnt = frame.getCount(); //(allImages == null ? 0 : allImages.getCount());
		boolean showNext = currentPosition < cnt - 1;

		boolean prevIsVisible = mPrevImageView.getVisibility() == View.VISIBLE;
		boolean nextIsVisible = mNextImageView.getVisibility() == View.VISIBLE;

		if (showPrev && !prevIsVisible) {
			Animation a = mShowPrevImageViewAnimation;
			a.setDuration(500);
			mPrevImageView.startAnimation(a);
			mPrevImageView.setVisibility(View.VISIBLE);
		} else if (!showPrev && prevIsVisible) {
			Animation a = mHidePrevImageViewAnimation;
			a.setDuration(500);
			mPrevImageView.startAnimation(a);
			mPrevImageView.setVisibility(View.GONE);
		}

		if (showNext && !nextIsVisible) {
			Animation a = mShowNextImageViewAnimation;
			a.setDuration(500);
			mNextImageView.startAnimation(a);
			mNextImageView.setVisibility(View.VISIBLE);
		} else if (!showNext && nextIsVisible) {
			Animation a = mHideNextImageViewAnimation;
			a.setDuration(500);
			mNextImageView.startAnimation(a);
			mNextImageView.setVisibility(View.GONE);
		}
	}

	private void updateZoomButtonsEnabled() {
		ImageViewTouch iv = mImageView;
		float scale = iv.getScale();
		ZoomButtonsController c = mZoomButtonsController;
		//
		c.setZoomInEnabled(scale < iv.mMaxZoom);
		c.setZoomOutEnabled(scale > iv.mMinZoom);
		//
	}

	private void showMessage(Exception e) {
		showMessage(e.getLocalizedMessage());
	}
	
	private void showMessage(String msg) {
		Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
	}

	private final KeyTracker mKeyTracker = new KeyTracker(this);

	public State onKeyTracker(int keyCode, KeyEvent event, Stage stage,
			int duration) {
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			//
			hideProgress();
			
//			if (stage == KeyTracker.Stage.LONG_REPEAT) {
//				doQuit();
//				return KeyTracker.State.DONE_TRACKING;
//			} else if (stage == KeyTracker.Stage.UP) {
//				doFinish();
//				return KeyTracker.State.DONE_TRACKING;
//			}
			return KeyTracker.State.KEEP_TRACKING;
		}
		return KeyTracker.State.NOT_TRACKING;
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		return mKeyTracker.doKeyUp(keyCode, event)
				|| super.onKeyUp(keyCode, event);
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		boolean handled = mKeyTracker.doKeyDown(keyCode, event);
		return (handled || super.onKeyDown(keyCode, event));
	}
	
	private Handler errorHandler = new Handler() {
		public void handleMessage(Message msg) {
			Object o = msg.obj;
			if (msg.what == -1 && o != null) {
				try {
					String txt = ((Exception) o).getMessage();
					if (Debug.DEBUG) {
						Log.d(TAG, "showError " + txt);
					}
					Toast.makeText(PhotoFrameActivity.this, txt, Toast.LENGTH_SHORT).show();
				} catch (Throwable t) {
					Log.d(TAG, "showError " + t);
				}
			}
		}
	};
	
	public boolean showBgError(Exception e) {
		if (Debug.DEBUG) {
			Log.d(TAG, "showBgError: " + e);
		}
		Message msg = Message.obtain(errorHandler);
		msg.arg1 = -1;
		msg.obj = e;
		return errorHandler.sendMessage(msg);
	}

//	private void doFinish() {
//		if (DEBUG) {
//			Log.d(TAG, "doFinish");
//		}
//		finish();
//	}

	// @Override
	// public boolean onKeyDown(int keyCode, KeyEvent event) {
	// if (keyCode == KeyEvent.KEYCODE_BACK) {
	// doFinish();
	// return true;
	// }
	// return super.onKeyDown(keyCode, event);
	// }
}